package cn.com.yang.algorithm.v4;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Test;

import com.alibaba.fastjson.JSON;


/**
 * @Author 杨亿金
 * @Date 2022/6/15 22:42
 * @CopyRight
 * @Version
 */
public class SM2Util {
    /**
     * SM2算法生成密钥对
     * @return 密钥对信息
     */
    public static KeyPair generateSm2KeyPair() {
        try {
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            // 获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            // 使用SM2的算法区域初始化密钥生成器
            kpg.initialize(sm2Spec, new SecureRandom());
            // 获取密钥对
            return kpg.generateKeyPair();
        } catch (Exception e) {
            System.out.println("generate sm2 key pair failed:{}"+e.getMessage());
            return  null;
        }
    }

    /**
     * @Description 公钥加密
     * @Author msx
     * @param publicKeyHex SM2十六进制公钥
     * @param data         明文数据
     * @return String
     */
    public static String encrypt(String publicKeyHex, String data,int mode) {
        return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, mode);
    }
    /**
     * @Description 公钥加密
     * @Author msx
     * @param publicKey SM2公钥
     * @param data      明文数据
     * @param modeType  加密模式
     * @return String
     */
    public static String encrypt(BCECPublicKey publicKey, String data, int modeType) {
        //加密模式
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
        if (modeType != 1) {
            mode = SM2Engine.Mode.C1C2C3;
        }
        //通过公钥对象获取公钥的基本域参数。
        ECParameterSpec ecParameterSpec = publicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过公钥值和公钥基本参数创建公钥参数对象
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
        //根据加密模式实例化SM2公钥加密引擎
        SM2Engine sm2Engine = new SM2Engine(mode);
        //初始化加密引擎
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
        byte[] arrayOfBytes = null;
        try {
            //将明文字符串转换为指定编码的字节串
            byte[] in = data.getBytes("utf-8");
            //通过加密引擎对字节数串行加密
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
            System.out.println("SM2加密时出现异常:" + e.getMessage());
            e.printStackTrace();
        }
        //将加密后的字节串转换为十六进制字符串
        return Hex.toHexString(arrayOfBytes);
    }
    //椭圆曲线ECParameters ASN.1 结构
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    //椭圆曲线公钥或私钥的基本域参数。
    private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    /**
     * @Description 公钥字符串转换为 BCECPublicKey 公钥对象
     * @Author msx
     * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04：表示该公钥为非压缩格式，操作时需要删除)
     * @return BCECPublicKey SM2公钥对象
     */
    public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
        //截取64字节有效的SM2公钥（如果公钥首个字节为0x04）
        if (pubKeyHex.length() > 128) {
            pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
        }
        //将公钥拆分为x,y分量（各32字节）
        String stringX = pubKeyHex.substring(0, 64);
        String stringY = pubKeyHex.substring(stringX.length());
        //将公钥x、y分量转换为BigInteger类型
        BigInteger x = new BigInteger(stringX, 16);
        BigInteger y = new BigInteger(stringY, 16);
        //通过公钥x、y分量创建椭圆曲线公钥规范
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
        //通过椭圆曲线公钥规范，创建出椭圆曲线公钥对象（可用于SM2加密及验签）
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * @Description 私钥字符串转换为 BCECPrivateKey 私钥对象
     * @Author msx
     * @param privateKeyHex 32字节十六进制私钥字符串
     * @return BCECPrivateKey SM2私钥对象
     */
    public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
        //将十六进制私钥字符串转换为BigInteger对象
        BigInteger d = new BigInteger(privateKeyHex, 16);
        //通过私钥和私钥域参数集创建椭圆曲线私钥规范
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
        //通过椭圆曲线私钥规范，创建出椭圆曲线私钥对象（可用于SM2解密和签名）
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * @Description 私钥解密
     * @Author msx
     * @param privateKeyHex SM2十六进制私钥
     * @param cipherData    密文数据
     * @return String
     */
    public static String decrypt(String privateKeyHex, String cipherData) {
        return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);
    }
    /**
     * @Description 私钥解密
     * @Author msx
     * @param privateKey SM私钥
     * @param cipherData 密文数据
     * @param modeType   解密模式
     * @return
     */
    public static String decrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {
        //解密模式
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
        if (modeType != 1)
            mode = SM2Engine.Mode.C1C2C3;
        //将十六进制字符串密文转换为字节数组（需要与加密一致，加密是：加密后的字节数组转换为了十六进制字符串）
        byte[] cipherDataByte = Hex.decode(cipherData);
        //通过私钥对象获取私钥的基本域参数。
        ECParameterSpec ecParameterSpec = privateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过私钥值和私钥钥基本参数创建私钥参数对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
                ecDomainParameters);
        //通过解密模式创建解密引擎并初始化
        SM2Engine sm2Engine = new SM2Engine(mode);
        sm2Engine.init(false, ecPrivateKeyParameters);
        String result = null;
        try {
            //通过解密引擎对密文字节串进行解密
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            //将解密后的字节串转换为utf8字符编码的字符串（需要与明文加密时字符串转换成字节串所指定的字符编码保持一致）
            result = new String(arrayOfBytes, "utf-8");
        } catch (Exception e) {
            System.out.println("SM2解密时出现异常" + e.getMessage());
        }
        return result;
    }
    public static void main(String[] args) {
//        KeyPair par = generateSm2KeyPair();
//        String pubKey  =Base64.getEncoder().encodeToString(par.getPublic().getEncoded());
//        System.out.println("公钥：>>"+pubKey);
//        String priKey = Base64.getEncoder().encodeToString(par.getPrivate().getEncoded());
//        System.out.println("私钥：>>"+ priKey);

        KeyPair keyPair = generateSm2KeyPair();
        PublicKey publicKey = keyPair.getPublic();
        String publicKeyHex = null;
        String privateKeyHex = null;
        if (publicKey instanceof BCECPublicKey) {
            //获取65字节非压缩缩的十六进制公钥串(0x04)
            publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
            System.out.println("---->SM2公钥：" + publicKeyHex);
        }
        PrivateKey privateKey = keyPair.getPrivate();
        if (privateKey instanceof BCECPrivateKey) {
            //获取32字节十六进制私钥串
            privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
            System.out.println("---->SM2私钥：" + privateKeyHex);
        }
       
        /**
                          * 公钥加密
         */
        List<Map> list = new ArrayList();
        Map map1 = new HashMap();
        map1.put("name","yangyijin");
        Map map2 = new HashMap();
        map2.put("name","lihua");
        Map map3 = new HashMap();
        map3.put("name","zhansgan");
        list.add(map1);
        list.add(map2);
        list.add(map3);
        //String data = list.toString();
        String data = "[{OrgId=0102, AddrDtl=中华北大街370北郡A区底商河北银行北郡支行, Phone=13073125275, TotalPrice=806.6, OrgName=石家庄分行北郡支行, Buyer=杨平, RcvAddr=[Ljava.lang.String;@77c96a80, "
        		+ "		OrderInfo=[{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240}, "
        		+ "{Price=4.4, ProdName=小宝贝3连包抽取式面巾纸, Num=64, ProdId=P2022030900007241},"
        		+ "{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240},"
        		+ "{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240},"
        		+ "{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240},"
        		+ "{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240},"
        		+ "{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240},"
        		+ "{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240},"
        		+ "{Price=12.5, ProdName=小宝贝海洋铂金装5层10卷无芯卫生纸, Num=42, ProdId=P2022030900007240}], OrderId=D20220614151243222850000000020}]";
        //将十六进制公钥串转换为 BCECPublicKey 公钥对象
        Map map = new HashMap();
		map.put("OrderId","D20220406120212064560000000002");
        //String data = "{\"OrderId\":\"hb123456\",\"OrgId\":\"123\",\"OrgName\":\"机构号为123的机构名称\",\"Buyer\":\"测试人员\",\"Phone\":\"199999999999\",\"OrderInfo\":[{\"ProdId\":\"1\",\"ProdName\":\"测试商品1测试商品1测试商品1测试商品1测试商品1测试商品1测试商品1\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"2\",\"ProdName\":\"测试商品2测试商品2测试商品2测试商品2测试商品2测试商品2测试商品2\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"3\",\"ProdName\":\"测试商品3测试商品3测试商品3测试商品3测试商品3测试商品3\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"4\",\"ProdName\":\"测试商品4测试商品4测试商品4测试商品4测试商品4测试商品4\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"5\",\"ProdName\":\"测试商品5测试商品5测试商品5测试商品5测试商品5测试商品5\",\"Price\":\"200.00\",\"Num\":\"1\"}],\"RcvAddr\":[\"河北省\",\"石家庄市\",\"桥西区\"],\"AddrDtl\":\"金创大厦\",\"TotalPrice\":\"1000.00\"}";
        long start =System.currentTimeMillis();
        //德众
        //publicKeyHex = "046d0fe7b7b1861344324255ea5899a86396646f5e48c096ffcf8581fb9713b1962045400fc838c30d0df990f5515f826a847f6c6222b6831e7e1db509dd17903c";
        // 平南 
        publicKeyHex = "045a4eb33c1d39dbf02868893ee10b6a4dfb0ba8f3bd54d51dfb22698a26982d9b4e5e76d1f51ace1094c4a95bffd82bea22b0ae59a4a8f1f4fd18a47279d6945e";
        String encryptData = encrypt(publicKeyHex, data,0);
        System.out.println("加密结果>>>" + encryptData);

        /*** 私钥解密
         */
        //将十六进制私钥串转换为 BCECPrivateKey 私钥对象
       //德众
        //rivateKeyHex = "a5fc8119107ea267bc897ba3175d7223ceeac1fdda95e13fc239e1b71430befc";
        // 平南
        privateKeyHex = "2e88cfb72f58ec55765dcb4f563a790a73c1c742440243dd75adcaf51b8374cd";
        data = decrypt(privateKeyHex, encryptData);
        System.out.println("解密结果>>>" + data);
        long end =System.currentTimeMillis();
        System.out.println("加解密耗时>>>"+(end-start)+"ms");
    }
    
    /**
     * 加密
     */
    @Test
    public void testEn() {
    	 String data = "{\"OrderId\":\"hb123456\",\"OrgId\":\"123\",\"OrgName\":\"机构号为123的机构名称\",\"Buyer\":\"测试人员\",\"Phone\":\"199999999999\",\"OrderInfo\":[{\"ProdId\":\"1\",\"ProdName\":\"测试商品1测试商品1测试商品1测试商品1测试商品1测试商品1测试商品1\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"2\",\"ProdName\":\"测试商品2测试商品2测试商品2测试商品2测试商品2测试商品2测试商品2\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"3\",\"ProdName\":\"测试商品3测试商品3测试商品3测试商品3测试商品3测试商品3\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"4\",\"ProdName\":\"测试商品4测试商品4测试商品4测试商品4测试商品4测试商品4\",\"Price\":\"200.00\",\"Num\":\"1\"},{\"ProdId\":\"5\",\"ProdName\":\"测试商品5测试商品5测试商品5测试商品5测试商品5测试商品5\",\"Price\":\"200.00\",\"Num\":\"1\"}],\"RcvAddr\":[\"河北省\",\"石家庄市\",\"桥西区\"],\"AddrDtl\":\"金创大厦\",\"TotalPrice\":\"1000.00\"}";
         long start =System.currentTimeMillis();
         //德众
         //String publicKeyHex = "046d0fe7b7b1861344324255ea5899a86396646f5e48c096ffcf8581fb9713b1962045400fc838c30d0df990f5515f826a847f6c6222b6831e7e1db509dd17903c";
         //平南
         String publicKeyHex = "045a4eb33c1d39dbf02868893ee10b6a4dfb0ba8f3bd54d51dfb22698a26982d9b4e5e76d1f51ace1094c4a95bffd82bea22b0ae59a4a8f1f4fd18a47279d6945e";
         String encryptData = encrypt(publicKeyHex, data,1);
         System.out.println("加密结果>>>" + encryptData);
         long end =System.currentTimeMillis();
         System.out.println("加密耗时>>>"+(end-start)+"ms");
    }
    /**
     * 解密
     */
    @Test
    public void testDe() {
    	 long start =System.currentTimeMillis();
    	//德众
    	String privateKeyHex = "3cfb7eba19acc7c414fe27a635f9b3bc1ec021223ff72e38a26c05fb20dd73fa";
        String encryptData ="047da82b2894d7a7aa39c7ef0087fe2b97d3be62d7baa413ef4426cf24579fdea81057916e1dba90fbba94d55b79247391a20f4357a0a7ea6feaeb2951b062a95de129f5998f977d01944e96de434a30d68a1673b848b7764409d316804c9962f8e2811592c8c55351d0054c42877e1ed928f87447f07028f57ea23bd82f7242c81d921f5e671e2361b07fabff";
        String data = decrypt(privateKeyHex, encryptData);
        System.out.println("解密结果>>>" + data);
        System.out.println("解密结果>>>"+new String(Base64.getDecoder().decode(data)));
        long end =System.currentTimeMillis();
        System.out.println("解密耗时>>>"+(end-start)+"ms");
    }
    
    @Test
    public void Testzijei() {
    	//String str ="{\"OrgId\":\"0198\",\"AddrDtl\":\"红旗街道红旗路\",\"Phone\":\"13676967290\",\"TotalPrice\":19.9,\"OrgName\":\"河北银行石家庄分行\",\"Buyer\":\"杨亿金\",\"RcvAddr\":[\"河北省\",\"石家庄市\",\"桥西区\"],\"OrderInfo\":[{\"Price\":19.9,\"ProdName\":\"福临门家宴小麦粉优质5kg\",\"Num\":1,\"ProdId\":\"P2022030900007230\"}],\"OrderId\":\"D20220730171556064420000000000\"};"
    	String str="{\"OrgId\":\"0198\",\"AddrDtl\":\"红旗街道红旗路\",\"Phone\":\"13676967290\",\"TotalPrice\":19.9,\"OrgName\":\"河北银行石家庄分行\",\"Buyer\":\"杨亿金\",\"RcvAddr\":[\"河北省\",\"石家庄市\",\"桥西区\"],\"OrderInfo\":[{\"Price\":19.9,\"ProdName\":\"福临门家宴小麦粉优质5kg\",\"Num\":1,\"Prod";
    	System.out.println(str.getBytes().length);
    	String zh="红旗街道红旗路河北银行石家庄分行杨亿金河北省石家庄市桥西区福临门家宴小麦粉优质";
    	System.out.println("汉字："+zh.length());
    }
}
